package com.jzbooks.utils;

import com.jzbooks.enums.ResultCodeEnum;
import com.jzbooks.exception.BusinessException;
import io.jsonwebtoken.*;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author mingPeng
 * @date 2021/4/25 21:41
 * @Description : jwt工具类
 */
@Component
public class JwtUtil {


    /***
     * 配置文件中过期时间,默认为30天
     */
    @Value("${jwt.expirationDate}")
    public Long expirationDate;


    /***
     * 配置文件中的key
     */
    @Value("${jwt.key}")
    public String jwtKey;


    /**
     * 过期时间的时间单位
     */
    @Value("${jwt.timeUnit}")
    protected TimeUnit timeUnit = TimeUnit.DAYS;


    /**
     * 请求头
     */
    @Value("${jwt.tokenHeaderName}")
    private String tokenHeaderName;

    @Autowired
    @Qualifier("RedisUtil")
    private RedisUtil redisUtil;


    /**
     * @param userId 如果是用户表，存的userId是学号，管理员表，存的是主键
     * @param type   存储的用户类型，0 为管理员，1为老师，2为学生
     * @return
     * @Description : 生成 token
     */
    public String createToken(Integer userId, Integer type) {
        //创建payload的私有声明
        Map<String, Object> claims = new HashMap<String, Object>(2);
        claims.put("userId", userId);
        claims.put("type", type);

        //指定签名的时候使用的签名算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //生成签名的时候使用的秘钥secret
        SecretKey key = generalKey();

        //创建jwt
        JwtBuilder builder = Jwts.builder()
                .setClaims(claims)
                .setId(UUID.randomUUID().toString())
                .signWith(signatureAlgorithm, key);
        //设置过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE, expirationDate.intValue());
//        instance.add(Calendar.SECOND, 30);
        builder.setExpiration(instance.getTime());
        //生成token
        String token = builder.compact();
        //存入redis
        redisUtil.set("user_id_token:" + userId + ":token", token, expirationDate, timeUnit);
        return token;
    }

    /**
     * @param token
     * @return
     * @Description : 解密jwt，得到payload
     */
    public Claims parseToken(String token) {
        //签名秘钥，和生成的签名的秘钥一模一样
        SecretKey key = generalKey();
        //返回的数据
        Claims claims;
        try {
            claims = Jwts.parser()  //得到DefaultJwtParser
                    .setSigningKey(key)         //设置签名的秘钥
                    .parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            // 过期的抛出异常
//            claims = e.getClaims();
            throw new BusinessException(ResultCodeEnum.ILLEGAL_TOKEN);
        } catch (Exception e) {
            claims = null;
        }//设置需要解析的jwt
        return claims;
    }

    /**
     * @param request
     * @return
     * @Description : 拿到前端的token，进行验证
     */
    public boolean verifyJWTAndRefreshExpireTime(HttpServletRequest request, HttpServletResponse response) {
        boolean verifyValue = false;
        //前端携带的token
        String token = request.getHeader(tokenHeaderName);
        //判断是否有token
        if (!StringUtils.isEmpty(token)) {
            //解析jwt，得到claims
            Claims claims = parseToken(token);

            if (claims != null) {
//                String jwtId = claims.getId();
                //得到用户id
                Integer userId = claims.get("userId", Integer.class);
                //得到用户类型
                Integer type =  claims.get("type", Integer.class);

                //拼凑redis key
                String key = "user_id_token:" + userId + ":token";

                String tokenValue = (String) redisUtil.get(key);
                //redis有token
                if (tokenValue != null && tokenValue.equals(token)) {
                    Long times = claims.getExpiration().getTime() - System.currentTimeMillis();
                    if (times < 0) {
                        String createToken = createToken(userId, type);
                        response.addHeader(tokenHeaderName, createToken);
                    } else {
                        //重新更新失效时间
                        redisUtil.expireOfTimeUnit(key, expirationDate, timeUnit);
                    }
                    verifyValue = true;
                }else{
                    //token不一致,包括没有token
                    verifyValue = false;
                }
            }
        }
    //没有token返回false
        return verifyValue;
    }

    /**
     * 从SpringContext获取Request，然后获取token,返回Claims
     *
     * @return Claims
     */
    public Claims getClaimsFromToken() {
        String token = SpringUtil.getRequest().getHeader(tokenHeaderName);
        return parseToken(token);
    }

    /**
     * @param null
     * @return
     * @Description :  从token的claims里获取userId
     */
    public Integer getUserIdFromToken() {
        return (Integer) getClaimsFromToken().get("userId");
    }

    /**
     * @param null
     * @return
     * @Description : 从token的claims里获取type
     */
    public Integer getTypeFromToken() {
        return (Integer) getClaimsFromToken().get("type");
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public SecretKey generalKey() {
        //本地配置文件中加密的密文
        String stringKey = jwtKey;
        //本地的密码解码
        byte[] encodedKey = Base64.decodeBase64(stringKey);
        // 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 length 个字节这是当然是所有。
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

}
